/**
 * Copyright 2008 - 2012
 * 
 * Licensed under the Apache License, Version SIZEOF_SHORT.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-SIZEOF_SHORT.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 * 
 * @project loon
 * @author cping
 * @email：javachenpeng@yahoo.com
 * @version 0.3.3
 */
package org.loon.framework.jni;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;

import android.util.Log;

/**
 * 自0.3.3起，将部分耗时代码本地化。如果携带有lplus库时，将调用so文件，否则依旧纯Java。
 * 用户可有选择的使用。
 */
public final class NativeSupport {

	public static final int SIZEOF_BYTE = 1;

	public static final int SIZEOF_SHORT = 2;

	public static final int SIZEOF_FLOAT = 4;

	public static final int SIZEOF_INT = SIZEOF_FLOAT;

	public static final int SIZEOF_DOUBLE = 8;

	public static final int SIZEOF_LONG = SIZEOF_DOUBLE;

	private static boolean useLoonNative;

	static {
		try {
			System.loadLibrary("lplus");
			Log.i("LoonLib", "Support of the native method call");
			useLoonNative = true;
		} catch (Error e) {
			useLoonNative = false;
		}
	}

	public static boolean UseLoonNative() {
		return useLoonNative;
	}

	public static void CloseLoonNative() {
		useLoonNative = false;
	}

	public static void copy(float[] src, Buffer dst, int numFloats) {
		copy(src, dst, 0, numFloats);
	}

	public static void copy(float[] src, Buffer dst, int offset, int numFloats) {
		if (useLoonNative) {
			bufferCopy(src, dst, numFloats, offset);
			if (dst instanceof ByteBuffer) {
				dst.limit(numFloats << SIZEOF_SHORT);
			} else if (dst instanceof FloatBuffer) {
				dst.limit(numFloats);
			}
			dst.position(0);
		} else {
			putBuffer(dst, src, offset, numFloats);
			dst.position(0);
		}
	}

	public static IntBuffer newIntBuffer(final int[] src) {
		if (src == null) {
			return null;
		}
		int size = src.length;
		IntBuffer buffer = newIntBuffer(size);
		copy(src, 0, buffer, size);
		return buffer;
	}

	public static FloatBuffer newFloatBuffer(float[] src, int offset,
			int numFloats) {
		FloatBuffer buffer = newFloatBuffer(numFloats);
		copy(src, buffer, offset, numFloats);
		return buffer;
	}

	public static ByteBuffer clone(final ByteBuffer dst) {
		if (dst == null) {
			return null;
		}
		int size = dst.limit();
		ByteBuffer copy = newByteBuffer(size);
		copy(copy, dst, size);
		return copy;
	}

	public static FloatBuffer clone(final FloatBuffer dst) {
		if (dst == null) {
			return null;
		}
		int size = dst.limit();
		FloatBuffer copy = newFloatBuffer(size);
		copy(copy, dst, size);
		return copy;
	}

	public static void copy(byte[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset, dst, positionInBytes(dst), numElements);
			dst.limit(dst.position() + bytesToElements(dst, numElements));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(short[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset << SIZEOF_BYTE, dst,
					positionInBytes(dst), numElements << SIZEOF_BYTE);
			dst.limit(dst.position()
					+ bytesToElements(dst, numElements << SIZEOF_BYTE));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(char[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset << SIZEOF_BYTE, dst,
					positionInBytes(dst), numElements << SIZEOF_BYTE);
			dst.limit(dst.position()
					+ bytesToElements(dst, numElements << SIZEOF_BYTE));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(int[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset << SIZEOF_SHORT, dst,
					positionInBytes(dst), numElements << SIZEOF_SHORT);
			dst.limit(dst.position()
					+ bytesToElements(dst, numElements << SIZEOF_SHORT));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(long[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset << 3, dst, positionInBytes(dst),
					numElements << 3);
			dst.limit(dst.position() + bytesToElements(dst, numElements << 3));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(float[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset << SIZEOF_SHORT, dst,
					positionInBytes(dst), numElements << SIZEOF_SHORT);
			dst.limit(dst.position()
					+ bytesToElements(dst, numElements << SIZEOF_SHORT));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(double[] src, int srcOffset, Buffer dst,
			int numElements) {
		if (useLoonNative) {
			bufferCopy(src, srcOffset << 3, dst, positionInBytes(dst),
					numElements << 3);
			dst.limit(dst.position() + bytesToElements(dst, numElements << 3));
		} else {
			putBuffer(dst, src, srcOffset, numElements);
		}
	}

	public static void copy(Buffer src, Buffer dst, int numElements) {
		if (useLoonNative) {
			int numBytes = elementsToBytes(src, numElements);
			bufferCopy(src, positionInBytes(src), dst, positionInBytes(dst),
					numBytes);
			dst.limit(dst.position() + bytesToElements(dst, numBytes));
		} else {
			putBuffer(dst, src, numElements);
		}
	}

	private static int positionInBytes(Buffer dst) {
		if (dst instanceof ByteBuffer) {
			return dst.position();
		} else if (dst instanceof ShortBuffer) {
			return dst.position() << SIZEOF_BYTE;
		} else if (dst instanceof CharBuffer) {
			return dst.position() << SIZEOF_BYTE;
		} else if (dst instanceof IntBuffer) {
			return dst.position() << SIZEOF_SHORT;
		} else if (dst instanceof LongBuffer) {
			return dst.position() << 3;
		} else if (dst instanceof FloatBuffer) {
			return dst.position() << SIZEOF_SHORT;
		} else if (dst instanceof DoubleBuffer) {
			return dst.position() << 3;
		} else {
			throw new RuntimeException("Can't copy to a "
					+ dst.getClass().getName() + " instance");
		}
	}

	private static int bytesToElements(Buffer dst, int bytes) {
		if (dst instanceof ByteBuffer) {
			return bytes;
		} else if (dst instanceof ShortBuffer) {
			return bytes >>> SIZEOF_BYTE;
		} else if (dst instanceof CharBuffer) {
			return bytes >>> SIZEOF_BYTE;
		} else if (dst instanceof IntBuffer) {
			return bytes >>> SIZEOF_SHORT;
		} else if (dst instanceof LongBuffer) {
			return bytes >>> 3;
		} else if (dst instanceof FloatBuffer) {
			return bytes >>> SIZEOF_SHORT;
		} else if (dst instanceof DoubleBuffer) {
			return bytes >>> 3;
		} else {
			throw new RuntimeException("Can't copy to a "
					+ dst.getClass().getName() + " instance");
		}
	}

	private static int elementsToBytes(Buffer dst, int elements) {
		if (dst instanceof ByteBuffer) {
			return elements;
		} else if (dst instanceof ShortBuffer) {
			return elements << SIZEOF_BYTE;
		} else if (dst instanceof CharBuffer) {
			return elements << SIZEOF_BYTE;
		} else if (dst instanceof IntBuffer) {
			return elements << SIZEOF_SHORT;
		} else if (dst instanceof LongBuffer) {
			return elements << 3;
		} else if (dst instanceof FloatBuffer) {
			return elements << SIZEOF_SHORT;
		} else if (dst instanceof DoubleBuffer) {
			return elements << 3;
		} else {
			throw new RuntimeException("Can't copy to a "
					+ dst.getClass().getName() + " instance");
		}
	}

	private static void putBuffer(Buffer dst, Object src, int offset,
			int numFloats) {
		if (dst instanceof ByteBuffer) {
			byte[] buffer = (byte[]) src;
			ByteBuffer writer = (ByteBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else if (dst instanceof ShortBuffer) {
			short[] buffer = (short[]) src;
			ShortBuffer writer = (ShortBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else if (dst instanceof CharBuffer) {
			char[] buffer = (char[]) src;
			CharBuffer writer = (CharBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else if (dst instanceof IntBuffer) {
			int[] buffer = (int[]) src;
			IntBuffer writer = (IntBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else if (dst instanceof LongBuffer) {
			long[] buffer = (long[]) src;
			LongBuffer writer = (LongBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else if (dst instanceof FloatBuffer) {
			float[] buffer = (float[]) src;
			FloatBuffer writer = (FloatBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else if (dst instanceof DoubleBuffer) {
			double[] buffer = (double[]) src;
			DoubleBuffer writer = (DoubleBuffer) dst;
			writer.limit(numFloats);
			writer.put(buffer, offset, numFloats);
		} else {
			throw new RuntimeException("Can't copy to a "
					+ dst.getClass().getName() + " instance");
		}
		dst.position(0);
	}

	private static void putBuffer(Buffer dst, Buffer src, int numFloats) {
		if (dst instanceof ByteBuffer) {
			ByteBuffer buffer = (ByteBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((ByteBuffer) src);
		} else if (dst instanceof ShortBuffer) {
			ShortBuffer buffer = (ShortBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((ShortBuffer) src);
		} else if (dst instanceof CharBuffer) {
			CharBuffer buffer = (CharBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((CharBuffer) src);
		} else if (dst instanceof IntBuffer) {
			IntBuffer buffer = (IntBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((IntBuffer) src);
		} else if (dst instanceof LongBuffer) {
			LongBuffer buffer = (LongBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((LongBuffer) src);
		} else if (dst instanceof FloatBuffer) {
			FloatBuffer buffer = (FloatBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((FloatBuffer) src);
		} else if (dst instanceof DoubleBuffer) {
			DoubleBuffer buffer = (DoubleBuffer) dst;
			buffer.limit(numFloats);
			buffer.put((DoubleBuffer) src);
		} else {
			throw new RuntimeException("Can't copy to a "
					+ dst.getClass().getName() + " instance");
		}
		dst.position(0);
	}

	public static ByteBuffer replaceBytes(ByteBuffer dst, float[] src) {
		int size = src.length;
		dst.clear();
		copy(src, 0, dst, size);
		dst.position(0);
		return dst;
	}

	public static FloatBuffer replaceFloats(FloatBuffer dst, float[] src) {
		int size = src.length;
		dst.clear();
		copy(src, 0, dst, size);
		dst.position(0);
		return dst;
	}

	public static ByteBuffer getByteBuffer(byte[] bytes) {
		if (useLoonNative) {
			final int size = bytes.length;
			ByteBuffer buffer = newByteBuffer(size);
			copy(bytes, 0, buffer, size);
			buffer.position(0);
			return buffer;
		} else {
			ByteBuffer buffer = newByteBuffer(bytes.length).put(bytes);
			buffer.position(0);
			return buffer;
		}
	}

	public static FloatBuffer getFloatBuffer(float[] floats) {
		if (useLoonNative) {
			final int size = floats.length;
			FloatBuffer buffer = newFloatBuffer(size);
			copy(floats, 0, buffer, size);
			buffer.position(0);
			return buffer;
		} else {
			FloatBuffer buffer = newFloatBuffer(floats.length).put(floats);
			buffer.position(0);
			return buffer;
		}
	}

	public static ByteBuffer newByteBuffer(int numBytes) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numBytes);
			buffer.order(ByteOrder.nativeOrder());
			return buffer;
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numBytes);
			buffer.order(ByteOrder.nativeOrder());
			return buffer;
		}
	}

	public static FloatBuffer newFloatBuffer(int numFloats) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numFloats * SIZEOF_FLOAT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asFloatBuffer();
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numFloats
					* SIZEOF_FLOAT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asFloatBuffer();
		}
	}

	public static DoubleBuffer newDoubleBuffer(int numDoubles) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numDoubles * SIZEOF_DOUBLE);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asDoubleBuffer();
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numDoubles
					* SIZEOF_DOUBLE);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asDoubleBuffer();
		}
	}

	public static ShortBuffer newShortBuffer(int numShorts) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numShorts * SIZEOF_SHORT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asShortBuffer();
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numShorts
					* SIZEOF_SHORT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asShortBuffer();
		}
	}

	public static CharBuffer newCharBuffer(int numChars) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numChars * SIZEOF_SHORT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asCharBuffer();
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numChars
					* SIZEOF_SHORT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asCharBuffer();
		}
	}

	public static IntBuffer newIntBuffer(int numInts) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numInts * SIZEOF_FLOAT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asIntBuffer();
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numInts
					* SIZEOF_FLOAT);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asIntBuffer();
		}
	}

	public static LongBuffer newLongBuffer(int numLongs) {
		if (useLoonNative) {
			ByteBuffer buffer = bufferAllocateDirect(numLongs * SIZEOF_DOUBLE);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asLongBuffer();
		} else {
			ByteBuffer buffer = ByteBuffer.allocateDirect(numLongs
					* SIZEOF_DOUBLE);
			buffer.order(ByteOrder.nativeOrder());
			return buffer.asLongBuffer();
		}
	}

	public static void put(final Buffer buffer, final float[] source,
			final int offset, final int length) {
		if (useLoonNative) {
			bufferPut(buffer, source, length, offset);
			buffer.position(0);
			buffer.limit(length << SIZEOF_SHORT);
		} else {
			putBuffer(buffer, source, offset, length);
		}
	}

	public static ByteBuffer allocateDirect(final int capacity) {
		if (useLoonNative) {
			return bufferAllocateDirect(capacity);
		} else {
			return ByteBuffer.allocateDirect(capacity);
		}
	}

	public static void freeMemory(Buffer buffer) {
		if (useLoonNative) {
			bufferFreeDirect(buffer);
		} else {
			buffer = null;
		}
	}

	public static void clear(Buffer buffer) {
		if (useLoonNative) {
			bufferClear(buffer, buffer.limit());
		} else {
			buffer.clear();
		}
	}

	public final static void filterColor(int maxPixel, int pixelStart,
			int pixelEnd, int[] src, int[] dst, int[] colors, int c1, int c2) {
		if (useLoonNative) {
			if (src == null) {
				return;
			}
			updateArray(maxPixel, pixelStart, pixelEnd, src, dst, colors, c1,
					c2);
		} else {
			final int length = src.length;
			if (pixelStart < pixelEnd) {
				final int start = pixelStart + 1;
				final int end = pixelEnd + 1;
				if (end > maxPixel) {
					return;
				}
				for (int i = 0; i < length; i++) {
					if (dst[i] != 0xffffff) {
						for (int pixIndex = start; pixIndex < end; pixIndex++) {
							if (colors[pixIndex] == src[i]) {
								dst[i] = 0xffffff;
							} else if (src[i] == c1) {
								dst[i] = 0xffffff;
							}
						}
					}
				}
			} else {
				final int start = pixelEnd - 1;
				final int end = pixelStart;
				if (start < 0) {
					return;
				}
				for (int i = 0; i < length; i++) {
					if (dst[i] != 0xffffff) {
						for (int pixIndex = start; pixIndex < end; pixIndex++) {
							if (colors[pixIndex] == src[i]) {
								dst[i] = 0xffffff;
							} else if (src[i] == c2) {
								dst[i] = 0xffffff;
							}
						}
					}
				}
			}
		}
	}

	public static void filterFractions(int size, float[] fractions, int width,
			int height, int[] pixels, int numElements) {
		if (useLoonNative) {
			updateFractions(size, fractions, width, height, pixels, numElements);
		} else {
			int x, y;
			int idx = 0;
			for (int j = 0; j < size; j++) {
				idx = j * numElements;
				if (fractions[idx + 4] != 0xffffff) {
					if (fractions[idx + 5] <= 0) {
						fractions[idx + 0] += fractions[idx + 2];
						fractions[idx + 1] += fractions[idx + 3];
						fractions[idx + 3] += 0.1;
					} else {
						fractions[idx + 5]--;
					}
					x = (int) fractions[idx + 0];
					y = (int) fractions[idx + 1];
					if (x > -1 && y > -1 && x < width && y < height) {
						pixels[x + y * width] = (int) fractions[idx + 4];
					}
				}
			}
		}
	}

	private native static ByteBuffer bufferAllocateDirect(final int size);

	private native static void bufferClear(Buffer buffer, int numBytes);

	private native static void bufferFreeDirect(final Buffer buffer);

	private native static void bufferPut(final Buffer buffer,
			final float[] source, final int length, final int offset);

	private native static void bufferCopy(float[] src, Buffer dst,
			int numFloats, int offset);

	private native static void bufferCopy(byte[] src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void bufferCopy(char[] src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void bufferCopy(short[] src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void bufferCopy(int[] src, int srcOffset, Buffer dst,
			int dstOffset, int numBytes);

	private native static void bufferCopy(long[] src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void bufferCopy(float[] src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void bufferCopy(double[] src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void bufferCopy(Buffer src, int srcOffset,
			Buffer dst, int dstOffset, int numBytes);

	private native static void updateArray(int maxPixel, int pixelStart,
			int pixelEnd, int[] src, int[] dst, int[] colors, int c1, int c2);

	private native static void updateFractions(int size, float[] src,
			int width, int height, int[] dst, int numElements);
}
